package com.eonmind.user.service.impl;


import cn.dev33.satoken.stp.StpUtil;
import com.eonmind.common.contract.application.redis.service.RedisService;
import com.eonmind.common.contract.data.response.PageResponse;
import com.eonmind.common.contract.enums.user.UserLoginStatus;
import com.eonmind.common.contract.exception.BusinessException;
import com.eonmind.common.contract.mail.iface.EmailService;
import com.eonmind.common.contract.mail.templates.MailHtmlTemplate;
import com.eonmind.common.provider.entity.user.User;
import com.eonmind.common.provider.entity.user.UserLoginRecord;
import com.eonmind.user.contract.dto.UserInfoData;
import com.eonmind.user.converts.UserConvert;
import com.eonmind.user.dto.UserDto;
import com.eonmind.user.mapper.UserLoginRecordMapper;
import com.eonmind.user.mapper.UserMapper;
import com.eonmind.user.service.UserService;
import com.eonmind.user.utils.FetchUserLoginRecordUtil;
import com.eonmind.user.utils.JudgePasswordComplexityUtil;
import com.eonmind.user.utils.MD5Utils;
import com.eonmind.user.utils.VerificationUtils;
import com.mongoplus.model.PageResult;
import jakarta.annotation.Resource;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

@Service
@SuppressWarnings("all")
public class UserServiceImpl implements UserService {

    @Resource
    private EmailService emailService;

    @Resource
    private UserMapper userMapper;

    @Resource
    private RedisService redisService;

    @Resource
    private NoticeService noticeService;

    @Resource
    private UserLoginRecordMapper userLoginRecordMapper;

    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Override
    public boolean register(UserDto.UserRegisterRequest userRegisterRequest) {
        // 0. 判断参数是否为空
        if (ObjectUtils.isEmpty(userRegisterRequest)) {
            throw new IllegalArgumentException("注册请求参数不能为空");
        }

        // 1. 校验用户名是否存在
        checkUsernameExists(userRegisterRequest.getUsername());

        // 2. 校验邮箱是否存在
        checkEmailExists(userRegisterRequest.getEmail());

        // 3. 校验电话号是否存在
        checkPhoneNumberExists(userRegisterRequest.getPhoneNumber());

        // 4. 判断并设置密码强度
        Integer complexity = JudgePasswordComplexityUtil.judgePasswordComplexity(userRegisterRequest.getPassword());
        userRegisterRequest.setPasswordComplexity(complexity);

        // 5. 校验验证码并创建用户
        verifyAndCreateUser(userRegisterRequest);

        // 6. 初始化用户通知配置
        noticeService.initUserNoticePreference(userRegisterRequest.getUsername());
        return true;
    }

    /**
     * 校验用户名是否存在
     *
     * @param username 用户名
     */
    private void checkUsernameExists(String username) {
        if (userMapper.existsByUsername(username)) {
            throw new IllegalArgumentException("用户名已被占用");
        }
    }

    /**
     * 校验邮箱是否存在
     *
     * @param email 邮箱
     */
    private void checkEmailExists(String email) {
        if (userMapper.existsByEmail(email)) {
            throw new IllegalArgumentException("邮箱已被注册");
        }
    }

    /**
     * 校验电话号是否存在
     *
     * @param phoneNumber 电话号
     */
    private void checkPhoneNumberExists(String phoneNumber) {
        if (userMapper.existsByPhoneNumber(phoneNumber)) {
            throw new IllegalArgumentException("电话号码已被注册");
        }
    }


    /**
     * 从redis读取验证码并校验
     *
     * @param userRegisterRequest 注册请求
     */
    private void verifyAndCreateUser(UserDto.UserRegisterRequest userRegisterRequest) {
        // 获取Redis数据库验证码
        String registerVerificationCode = redisService.get("registerVerificationCode:" + userRegisterRequest.getEmail());
        // 校验用户输入的验证码是否正确
        if (!registerVerificationCode.equals(userRegisterRequest.getVerificationCode())) {
            throw new BusinessException("验证码错误");
        }

        // 创建用户并保存到数据库
        User user = UserConvert.convertToUser(userRegisterRequest);
        userMapper.save(user);
    }


    @Override
    public UserDto.UserLoginResponse login(UserDto.UserLoginRequest userLoginRequest, HttpServletRequest request) {
        // 0、定义返回实体
        UserDto.UserLoginResponse userLoginResponse = new UserDto.UserLoginResponse();
        // 0、判断参数是否为空
        if (ObjectUtils.isEmpty(userLoginRequest)) {
            throw new BusinessException("参数为空");
        }

        // 1、根据用户的userName或者邮箱查询用户实体
        User user = userMapper.selectUserByEmail(userLoginRequest.getEmail());
        if (user == null) {
            user = userMapper.selectUserByUserName(userLoginRequest.getUsername());
        }
        if (user == null) {
            user = userMapper.selectUserByPhone(userLoginRequest.getPhone());
        }
        // 2、判断能否查询出来的用户实体
        if (user == null) {
            // 2.1、不存在这个用户
            throw new BusinessException("用户不存在，登录失败");
        } else {

            Long userId = user.getId();
            // 2.2、存在这个用户
            // 2.2.1、根据用户传入的密码，对传入的密码进行摘要算法，拿到结果
            String encryptedPassword = encryptPassword(userLoginRequest.getPassword());

            // 2.2.2、结果跟查询出来的进行比对，是否一致
            if (encryptedPassword.equals(user.getPassword())) {
                // 2.2.2.1、一致，进行登录
                StpUtil.login(user.getId());

                // 2.2.2.1.1、获取登录后的token
                String tokenValue = StpUtil.getTokenValue();

                // 2.2.2.1.2、填充返回实体的内容
                BeanUtils.copyProperties(user, userLoginResponse);
                userLoginResponse.setToken(tokenValue);

                //异步记录用户登录日志
                threadPoolTaskExecutor.execute(() -> {
                    saveUserLoginRecord(request, userId, UserLoginStatus.SUCCESS.getCode());
                });
                return userLoginResponse;
            } else {
                //异步保存登陆失败记录
                threadPoolTaskExecutor.execute(() -> {
                    saveUserLoginRecord(request, userId, UserLoginStatus.FAIL.getCode());
                });


                // 2.2.2.2、不一致，抛出异常
                throw new BusinessException("用户信息错误，登录失败");
            }

        }
    }


    private UserLoginRecord saveUserLoginRecord(HttpServletRequest request, Long userId, Integer status) {
        AsyncContext asyncContext = request.startAsync();
        // 设置超时时间，避免无限等待
        asyncContext.setTimeout(10000); // 30秒
        UserLoginRecord userLoginRecord = FetchUserLoginRecordUtil.getUserLoginRecord(request);
        asyncContext.complete();
        userLoginRecord.setUserId(userId);
        //保存成功登录记录
        userLoginRecord.setLoginStatus(status);
        userLoginRecordMapper.saveUserLoginRecord(userLoginRecord);
        return userLoginRecord;
    }

    @Override
    public boolean update(UserDto.UserUpdateRequest userUpdateRequest) {
        //判断参数是否为空
        if (ObjectUtils.isEmpty(userUpdateRequest)) {
            throw new BusinessException("参数为空");
        }
        if (!userMapper.existsByEmail(userUpdateRequest.getEmail()) || ObjectUtils.isEmpty(userUpdateRequest)) {
            throw new BusinessException("用户不存在或参数错误!");
        }
        if (userUpdateRequest.getVerificationCode() == null || !userUpdateRequest.getVerificationCode().equals(redisService.get("updateVerificationCode:" + userUpdateRequest.getEmail()))) {
            throw new BusinessException("验证码错误");
        }
        userUpdateRequest.setNewPassword(encryptPassword(userUpdateRequest.getNewPassword()));
        //设置密码强度
        Integer passwordComplexity = JudgePasswordComplexityUtil.judgePasswordComplexity(userUpdateRequest.getNewPassword());
        userUpdateRequest.setPasswordComplexity(passwordComplexity);

        verifyAndUpdateUser(userUpdateRequest);
        return true;
    }

    private boolean verifyAndUpdateUser(UserDto.UserUpdateRequest userUpdateRequest) {
        // 获取Redis数据库验证码
        String updateVerificationCode = redisService.get("updateVerificationCode:" + userUpdateRequest.getEmail());
        // 校验用户输入的验证码是否正确
        if (!updateVerificationCode.equals(userUpdateRequest.getVerificationCode())) {
            throw new BusinessException("验证码错误");
        }

        // 修改用户密码
        userMapper.updatePassword(userUpdateRequest.getEmail(), userUpdateRequest.getNewPassword(), userUpdateRequest.getPasswordComplexity());
        return true;
    }

    //生成注册验证码
    @Override
    public void sendRegisterVerificationCode(String email) {
        //生成并发送验证码
        String verificationCode = VerificationUtils.generateRandomCode(6);
        emailService.sendHtmlMessage(email, "注册验证码",
                MailHtmlTemplate.getRegistrationTemplate("eonmind", verificationCode, 5));
        // 存储验证码至Redis数据库
        redisService.set("registerVerificationCode:" + email, verificationCode, 900L);
    }

    //生成更新验证码
    @Override
    public void sendUpdateVerificationCode(String email) {
        //生成并发送验证码
        String verificationCode = VerificationUtils.generateRandomCode(6);
        emailService.sendHtmlMessage(email, "验证码",
                MailHtmlTemplate.getPasswordResetTemplate("eonmind", verificationCode, 5));
        // 存储验证码至Redis数据库
        redisService.set("updateVerificationCode:" + email, verificationCode, 900L);
    }

    @Override
    public PageResponse<UserDto.UserLoginRecordData> pageUserLoginRecord(UserInfoData userInfoData, UserDto.UserLoginRecordPageRequest userLoginRecordPageRequest) {
        PageResult<UserLoginRecord> pageResult = userLoginRecordMapper.pageUserLoginRecord(userInfoData.getId(), userLoginRecordPageRequest.getPage());
        PageResponse<UserDto.UserLoginRecordData> pageResponse = new PageResponse<>(userLoginRecordPageRequest.getPage());
        pageResponse.setValue(userLoginRecordPageRequest.getPage(), pageResult.getTotalSize(), UserConvert.toUserLoginRecordDataList(pageResult.getContentData()));
        return pageResponse;
    }

    // 辅助方法：对密码进行摘要算法加密
    private String encryptPassword(String password) {
        if (password == null) {
            return null;
        }
        // 这里使用MD5加密，你可以根据需要选择其他加密算法
        return MD5Utils.encrypt(password);
    }

}